﻿using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Reflection;

namespace GenerateAccessors
{
	class MethodInfoAdapter
	{
		public readonly MethodBase MethodInfo;
		public readonly string Name;
		public readonly Type ReflectedType;
		public readonly bool IsReflectedTypePublic;
		public readonly bool IsPublic;
		public readonly bool IsStatic;
		public readonly Type ReturnType;

		private MethodInfoAdapter(MethodBase info)
		{
			MethodInfo = info;
			Name = info.Name;
			ReflectedType = info.ReflectedType;
			IsReflectedTypePublic = info.ReflectedType.IsEntirelyPublic();
			IsPublic = info.IsPublic;
			IsStatic = info.IsStatic;
		}

		public MethodInfoAdapter(MethodInfo info)
			: this((MethodBase)info)
		{
			ReturnType = info.ReturnType;
		}

		public MethodInfoAdapter(ConstructorInfo info)
			: this((MethodBase)info)
		{
			ReturnType = ReflectedType;
		}

		public CodeParameterDeclarationExpression[] ParameterDeclarationExpressionArray
		{
			get
			{
				var parameters = new List<CodeParameterDeclarationExpression>();

				foreach (var param in MethodInfo.GetParameters())
				{
					parameters.Add(new CodeParameterDeclarationExpression(
						Code.CreateTypeReference(param.ParameterType), param.Name));
				}

				return parameters.ToArray();
			}
		}

		public CodeExpression[] ArgumentReferenceExpressionArray
		{
			get
			{
				var parameters = new List<CodeExpression>();

				foreach (var param in MethodInfo.GetParameters())
				{
					if (param.ParameterType.IsPublic)
					{
						parameters.Add(new CodeArgumentReferenceExpression(param.Name));
					}
					else
					{
						parameters.Add(new CodeFieldReferenceExpression(
							new CodeArgumentReferenceExpression(param.Name), Constants.PrivateObjectField));
					}
				}

				return parameters.ToArray();
			}
		}

		public CodeExpression[] ArgumentTypeOfExpressionArray
		{
			get
			{
				var parameters = new List<CodeExpression>();

				if (MethodInfo.IsGenericMethod)
				{
					// Currently, for generic methods we only want generic parameters.
					// (To pass to MethodInfo.MakeGenericMethod())

					foreach (var param in MethodInfo.GetParameters())
					{
						if (param.ParameterType.IsGenericParameter)
						{
							// Generic parameter
							parameters.Add(new CodeTypeOfExpression(
								new CodeTypeReference(param.ParameterType.Name, CodeTypeReferenceOptions.GenericTypeParameter)));
						}
					}
				}
				else
				{
					foreach (var param in MethodInfo.GetParameters())
					{
						if (param.ParameterType.IsEntirelyPublic())
						{
							parameters.Add(new CodeTypeOfExpression(param.ParameterType));
						}
						else if (param.ParameterType.IsArray || param.ParameterType.IsGenericType)
						{
							// Generic type with at least one non-public generic argument
							parameters.Add(Code.CreateCodeToFetchPrivateType(param.ParameterType));
						}
						else
						{
							// Non-generic private type: create reference to e.g. 'PrivateTestClass_Accessor.PrivateType'.
							parameters.Add(new CodeFieldReferenceExpression(
								new CodeTypeReferenceExpression(param.ParameterType + Constants.AccessorSuffix), Constants.PrivateTypeField));
						}
					}
				}

				return parameters.ToArray();
			}
		}
	}
}
